/*-------------------------------------------------------------------------
 *
 * procsignal.c
 *	  进程间信号处理的例程
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/storage/ipc/procsignal.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <signal.h>
#include <unistd.h>

#include "access/parallel.h"
#include "port/pg_bitutils.h"
#include "commands/async.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "replication/walsender.h"
#include "storage/condition_variable.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/proc.h"
#include "storage/shmem.h"
#include "storage/smgr.h"
#include "storage/sinval.h"
#include "tcop/tcopprot.h"
#include "utils/memutils.h"


/*
 * SIGUSR1信号被多路复用以支持多种事件类型的信号传递。具体原因通过共享内存中的标志进行传递。我们为每一个可能的“原因”保持一个布尔标志，以便不同的原因可以同时发送到一个进程。（然而，如果同一个原因几乎同时被发送多次，该进程可能只观察到一次。）
 *
 * 每个想要接收信号的进程在ProcSignalSlots数组中注册其进程ID。该数组通过后端ID索引，以简化插槽分配，并避免在知道要发送信号的进程的后端ID时搜索数组。（我们确实支持不使用后端ID的信号传递，但效率稍低。）
 *
 * 这些标志实际上被声明为“volatile sig_atomic_t”以确保最大移植性。这应确保标志值的加载和存储是原子的，允许我们省去任何显式的锁定。
 *
 * pss_signalFlags用于设置我们无需跟踪目标进程是否处理了信号的情况，但有时我们需要确认，例如在进行全局状态更改时，直到所有后端都注意到这一点才能认为该更改完成。对于这种用例，我们在pss_barrierCheckMask中设置一个位，然后递增当前的“障碍生成”；当新的障碍生成（或更大）出现在每个进程的pss_barrierGeneration标志中时，我们知道消息已在所有地方被接收。
 */
typedef struct
{
	volatile pid_t pss_pid;
	volatile sig_atomic_t pss_signalFlags[NUM_PROCSIGNALS];
	pg_atomic_uint64 pss_barrierGeneration;
	pg_atomic_uint32 pss_barrierCheckMask;
	ConditionVariable pss_barrierCV;
} ProcSignalSlot;

/*
 * 整个ProcSignal系统的全局信息可以存储在这里。
 *
 * psh_barrierGeneration是现存的最高障碍生成。
 */
typedef struct
{
	pg_atomic_uint64 psh_barrierGeneration;
	ProcSignalSlot psh_slot[FLEXIBLE_ARRAY_MEMBER];
} ProcSignalHeader;

/*
 * 我们为每个可能的BackendId预留一个插槽，以及每种可能的辅助进程类型各预留一个插槽。（该方案假设同一时间不会有多于一个的任何辅助进程类型。）
 */
#define NumProcSignalSlots	(MaxBackends + NUM_AUXPROCTYPES)

/* 检查相关类型位是否在标志中设置。 */
#define BARRIER_SHOULD_CHECK(flags, type) \
	(((flags) & (((uint32) 1) << (uint32) (type))) != 0)

/* 从标志中清除相关类型位。 */
#define BARRIER_CLEAR_BIT(flags, type) \
	((flags) &= ~(((uint32) 1) << (uint32) (type)))

static ProcSignalHeader *ProcSignal = NULL;
static ProcSignalSlot *MyProcSignalSlot = NULL;

static bool fc_CheckProcSignal(ProcSignalReason fc_reason);
static void fc_CleanupProcSignalState(int fc_status, Datum fc_arg);
static void fc_ResetProcSignalBarrierBits(uint32 fc_flags);

/*
 * ProcSignalShmemSize
 *		计算ProcSignal所需的共享内存空间
 */
Size ProcSignalShmemSize(void)
{
	Size		fc_size;

	fc_size = mul_size(NumProcSignalSlots, sizeof(ProcSignalSlot));
	fc_size = add_size(fc_size, offsetof(ProcSignalHeader, psh_slot));
	return fc_size;
}

/*
 * ProcSignalShmemInit
 *		分配并初始化ProcSignal的共享内存
 */
void ProcSignalShmemInit(void)
{
	Size		fc_size = ProcSignalShmemSize();
	bool		fc_found;

	ProcSignal = (ProcSignalHeader *)
		ShmemInitStruct("ProcSignal", fc_size, &fc_found);

	/* 如果我们是第一个，进行初始化。 */
	if (!fc_found)
	{
		int			fc_i;

		pg_atomic_init_u64(&ProcSignal->psh_barrierGeneration, 0);

		for (fc_i = 0; fc_i < NumProcSignalSlots; ++fc_i)
		{
			ProcSignalSlot *fc_slot = &ProcSignal->psh_slot[fc_i];

			fc_slot->pss_pid = 0;
			MemSet(fc_slot->pss_signalFlags, 0, sizeof(fc_slot->pss_signalFlags));
			pg_atomic_init_u64(&fc_slot->pss_barrierGeneration, PG_UINT64_MAX);
			pg_atomic_init_u32(&fc_slot->pss_barrierCheckMask, 0);
			ConditionVariableInit(&fc_slot->pss_barrierCV);
		}
	}
}

/*
 * ProcSignalInit
 *		在ProcSignal数组中注册当前进程
 *
 * 传递的索引应该是我的BackendId，如果进程有的话，
 * 否则为MaxBackends + 辅助进程类型。
 */
void ProcSignalInit(int fc_pss_idx)
{
	ProcSignalSlot *fc_slot;
	uint64		fc_barrier_generation;

	Assert(fc_pss_idx >= 1 && fc_pss_idx <= NumProcSignalSlots);

	fc_slot = &ProcSignal->psh_slot[fc_pss_idx - 1];

	/* 计算前一个数据项的长度 */
	if (fc_slot->pss_pid != 0)
		elog(LOG, "process %d taking over ProcSignal slot %d, but it's not empty",
			 MyProcPid, fc_pss_idx);

	/* 清除任何剩余的信号原因 */
	MemSet(fc_slot->pss_signalFlags, 0, NUM_PROCSIGNALS * sizeof(sig_atomic_t));

	/*
	 * 初始化屏障状态。由于我们是全新的进程，
	 * 不应该有任何需要更新的后端私有状态。因此，
	 * 我们可以广播最新的屏障代数，并忽略任何先前设置的检查位。
	 *
	 * 注意：这只有在初始化在启动序列中足够早的情况下有效，
	 * 我们还没有缓存任何可能需要失效的状态。这也是我们在这里
	 * 使用内存屏障的原因，以确保任何后续的内存读取严格发生在
	 * 之后。
	 */
	pg_atomic_write_u32(&fc_slot->pss_barrierCheckMask, 0);
	fc_barrier_generation =
		pg_atomic_read_u64(&ProcSignal->psh_barrierGeneration);
	pg_atomic_write_u64(&fc_slot->pss_barrierGeneration, fc_barrier_generation);
	pg_memory_barrier();

	/* 用我的PID标记槽位 */
	fc_slot->pss_pid = MyProcPid;

	/* 记住槽位位置以供CheckProcSignal使用 */
	MyProcSignalSlot = fc_slot;

	/* 设置在进程退出时释放槽位 */
	on_shmem_exit(fc_CleanupProcSignalState, Int32GetDatum(fc_pss_idx));
}

/*
 * CleanupProcSignalState
 *		从ProcSignal机制中移除当前进程
 *
 * 此函数通过on_shmem_exit()在后端关闭时调用。
 */
static void fc_CleanupProcSignalState(int fc_status, Datum fc_arg)
{
	int			fc_pss_idx = DatumGetInt32(fc_arg);
	ProcSignalSlot *fc_slot;

	fc_slot = &ProcSignal->psh_slot[fc_pss_idx - 1];
	Assert(fc_slot == MyProcSignalSlot);

	/*
	 * 清除MyProcSignalSlot，以便在此点之后接收到的SIGUSR1
	 * 不会在它不再属于我们的情况下尝试访问它（甚至可能
	 * 在我们解除映射共享内存段之后）。
	 */
	MyProcSignalSlot = NULL;

	/* 计算前一个数据项的长度 */
	if (fc_slot->pss_pid != MyProcPid)
	{
		/*
		 * 不要在这里出错。我们无论如何都要退出，
		 * 不希望陷入无限循环尝试退出
		 */
		elog(LOG, "process %d releasing ProcSignal slot %d, but it contains %d",
			 MyProcPid, fc_pss_idx, (int) fc_slot->pss_pid);
		return;					/* XXX 还是将槽位清零更好？ */
	}

	/*
	 * 使这个槽位看起来像是已经吸收了所有可能的屏障，
	 * 以便没有屏障等待阻塞在它上面。
	 */
	pg_atomic_write_u64(&fc_slot->pss_barrierGeneration, PG_UINT64_MAX);
	ConditionVariableBroadcast(&fc_slot->pss_barrierCV);

	fc_slot->pss_pid = 0;
}

/*
 * SendProcSignal
 *		向Postgres进程发送信号
 *
 * 提供backendId是可选的，但会加快操作速度。
 *
 * 成功时（信号已发送），返回零。
 * 出错时，返回-1，并设置errno（通常为ESRCH或EPERM）。
 *
 * 不要与ProcSendSignal混淆
 */
int SendProcSignal(pid_t fc_pid, ProcSignalReason fc_reason, BackendId fc_backendId)
{
	volatile ProcSignalSlot *fc_slot;

	if (fc_backendId != InvalidBackendId)
	{
		fc_slot = &ProcSignal->psh_slot[fc_backendId - 1];

		/*
		 * 注意：由于没有锁定，目标进程可能在这次测试之后
		 * 立即从共享内存分离并退出，在我们设置标志并发送信号之前。
		 * 并且信号槽可能甚至被新进程回收，因此我们可能会为
		 * 错误的进程设置标志。这没有关系，所有信号都
		 * 是这样的，如果错误触发也不会造成伤害。
		 */
		if (fc_slot->pss_pid == fc_pid)
		{
			/* 原子地设置适当的标志 */
			fc_slot->pss_signalFlags[fc_reason] = true;
			/* 发送信号 */
			return kill(fc_pid, SIGUSR1);
		}
	}
	else
	{
		/*
		 * 没有提供BackendId，因此使用PID在数组中搜索。
		 * 我们从后往前搜索数组，以减少搜索开销。
		 * 传递InvalidBackendId意味着目标很可能是辅助进程，
		 * 它将具有接近数组末尾的槽位。
		 */
		int			fc_i;

		for (fc_i = NumProcSignalSlots - 1; fc_i >= 0; fc_i--)
		{
			fc_slot = &ProcSignal->psh_slot[fc_i];

			if (fc_slot->pss_pid == fc_pid)
			{
				/* 上面的关于竞争条件的说明也适用于这里 */

				/* 原子地设置适当的标志 */
				fc_slot->pss_signalFlags[fc_reason] = true;
				/* 发送信号 */
				return kill(fc_pid, SIGUSR1);
			}
		}
	}

	errno = ESRCH;
	return -1;
}

/*
 * EmitProcSignalBarrier
 *		向每个Postgres进程发送信号
 *
 * 此函数的返回值是由此操作创建的屏障“代数”。
 * 此值可以传递给WaitForProcSignalBarrier，以等待
 * 直到已知ProcSignal机制中的每个参与者都已吸收信号
 * （或之后开始）。
 *
 * 请注意，将此用于频繁发生的任何事情都是个坏主意，
 * 因为中断每个后端可能会导致显著的性能下降。
 *
 * 调用者有权假设此函数不会抛出ERROR或FATAL。
 */
uint64 EmitProcSignalBarrier(ProcSignalBarrierType fc_type)
{
	uint32		fc_flagbit = 1 << (uint32) fc_type;
	uint64		fc_generation;

	/*
	 * 设置所有标志。
	 *
	 * 请注意，pg_atomic_fetch_or_u32具有完整的屏障语义，
	 * 因此这与调用者之前所做的任何操作和
	 * 我们之后所做的任何操作完全有序。（这对于之后调用的
	 * pg_atomic_add_fetch_u64也是正确的。）
	 */
	for (int fc_i = 0; fc_i < NumProcSignalSlots; fc_i++)
	{
		volatile ProcSignalSlot *fc_slot = &ProcSignal->psh_slot[fc_i];

		pg_atomic_fetch_or_u32(&fc_slot->pss_barrierCheckMask, fc_flagbit);
	}

	/*
	 * 增加代数计数器。
	 */
	fc_generation =
		pg_atomic_add_fetch_u64(&ProcSignal->psh_barrierGeneration, 1);

	/*
	 * 向所有进程发信号，以便它们更新其报告的屏障
	 * 代数。
	 *
	 * 这里的并发不是问题。已经退出的后端无关紧要，
	 * 自我们进入此函数以来加入的新后端必须已经有当前状态，
	 * 因为调用者负责确保在第一次调用此函数之前
	 * 相关状态是完全可见的。我们仍然需要叫醒他们 -
	 * 因为我们不能区分这些后端和需要更新状态的旧后端 -
	 * 但他们实际上不需要改变任何状态。
	 */
	for (int fc_i = NumProcSignalSlots - 1; fc_i >= 0; fc_i--)
	{
		volatile ProcSignalSlot *fc_slot = &ProcSignal->psh_slot[fc_i];
		pid_t		fc_pid = fc_slot->pss_pid;

		if (fc_pid != 0)
		{
			/* 参见SendProcSignal获取详细信息 */
			fc_slot->pss_signalFlags[PROCSIG_BARRIER] = true;
			kill(fc_pid, SIGUSR1);
		}
	}

	return fc_generation;
}

/*
 * WaitForProcSignalBarrier - 等待直到确保所有由对
 * EmitProcSignalBarrier()的特定调用请求的更改已生效。
 */
void WaitForProcSignalBarrier(uint64 fc_generation)
{
	Assert(fc_generation <= pg_atomic_read_u64(&ProcSignal->psh_barrierGeneration));

	elog(DEBUG1,
		 "waiting for all backends to process ProcSignalBarrier generation "
		 UINT64_FORMAT,
		 fc_generation);

	for (int fc_i = NumProcSignalSlots - 1; fc_i >= 0; fc_i--)
	{
		ProcSignalSlot *fc_slot = &ProcSignal->psh_slot[fc_i];
		uint64		fc_oldval;

		
/*
		 * 重要的是我们在这里只检查 pss_barrierGeneration，而不是 pss_barrierCheckMask。pss_barrierCheckMask 中的位会在屏障实际被吸收之前被清除，但 pss_barrierGeneration 只有在之后才会更新。
		 */
		fc_oldval = pg_atomic_read_u64(&fc_slot->pss_barrierGeneration);
		while (fc_oldval < fc_generation)
		{
			if (ConditionVariableTimedSleep(&fc_slot->pss_barrierCV,
											5000,
											WAIT_EVENT_PROC_SIGNAL_BARRIER))
				ereport(LOG,
						(errmsg("still waiting for backend with PID %lu to accept ProcSignalBarrier",
								(unsigned long) fc_slot->pss_pid)));
			fc_oldval = pg_atomic_read_u64(&fc_slot->pss_barrierGeneration);
		}
		ConditionVariableCancelSleep();
	}

	elog(DEBUG1,
		 "finished waiting for all backends to process ProcSignalBarrier generation "
		 UINT64_FORMAT,
		 fc_generation);

	/*
	 * 调用者可能调用这个函数是因为它想读取共享状态或在所有后端都已吸收屏障后对共享状态执行进一步的写入。然而，pss_barrierGeneration 的读取是在没有锁的情况下执行的；插入一个内存屏障以将其与后续的内容分开。
	 */
	pg_memory_barrier();
}

/*
 * 处理指示全局屏障事件的中断接收。
 *
 * 所有实际的工作都推迟到 ProcessProcSignalBarrier()，因为我们不能安全地在信号处理程序中访问屏障生成，因为 64 位原子操作可能会使用基于自旋锁的模拟，即使是读取时也是如此。由于这个例程只在发送 PROCSIG_BARRIER 时被调用，这不会导致很多不必要的工作。
 */
static void fc_HandleProcSignalBarrierInterrupt(void)
{
	InterruptPending = true;
	ProcSignalBarrierPending = true;
	/* latch 将由 procsignal_sigusr1_handler 设置 */
}

/*
 * 执行与全局屏障相关的中断检查。
 *
 * 任何参与 ProcSignal 信号的后端必须安排定期调用此函数。它是从 CHECK_FOR_INTERRUPTS() 调用的，这对正常的后端来说足够，但不一定对所有类型的后台进程有效。
 */
void ProcessProcSignalBarrier(void)
{
	uint64		fc_local_gen;
	uint64		fc_shared_gen;
	volatile uint32 fc_flags;

	Assert(MyProcSignalSlot);

	/* 如果没有工作要做则快速退出。 */
	if (!ProcSignalBarrierPending)
		return;
	ProcSignalBarrierPending = false;

	/*
	 * 处理多个障碍的情况并不罕见，可能在所有障碍的信号到达之前就开始处理。为了避免对后续信号的无谓响应，如果我们已经处理过所有障碍，则提前退出。
	 */
	fc_local_gen = pg_atomic_read_u64(&MyProcSignalSlot->pss_barrierGeneration);
	fc_shared_gen = pg_atomic_read_u64(&ProcSignal->psh_barrierGeneration);

	Assert(fc_local_gen <= fc_shared_gen);

	if (fc_local_gen == fc_shared_gen)
		return;

	/*
	 * 获取并清除为此后端设置的标志。注意，pg_atomic_exchange_u32是一个完整的障碍，因此我们可以保证上面对障碍生成的读取发生在我们原子提取标志之前，并且任何后续的状态变化发生在之后。
	 *
	 * 注意：为了避免竞争条件，我们必须先将pss_barrierCheckMask置零，然后再尝试进行障碍处理。如果我们反其道而行之，可能会在调用障碍处理函数之后但在清除标志位之前收到另一个类型的障碍信号。在这种情况下，我们将无法知道该标志位需要保持设置，因此再次调用障碍处理函数的需求将会被遗忘。因此，我们先暂时清除所有位，然后再将没有成功吸收的障碍位重新设置回来。
	 */
	fc_flags = pg_atomic_exchange_u32(&MyProcSignalSlot->pss_barrierCheckMask, 0);

	/*
	 * 如果没有设置标志，则可以跳过任何实际工作。否则，建立一个PG_TRY块，以便在发生错误时不丢失需要哪种类型障碍处理的记录。
	 */
	if (fc_flags != 0)
	{
		bool		fc_success = true;

		PG_TRY();
		{
			/*
			 * 处理每种类型的障碍。障碍处理函数通常应该返回true，但如果当前无法吸收障碍，则可能返回false。这应该是罕见的，因为这非常耗费资源。每次CHECK_FOR_INTERRUPTS()都会在这里返回，直到我们成功吸收障碍，这笔成本会迅速累积。
			 *
			 * 注意：无条件调用障碍处理函数应该是可以的，但更高效的做法是仅调用那些可能需要我们根据标志进行操作的函数。
			 */
			while (fc_flags != 0)
			{
				ProcSignalBarrierType fc_type;
				bool		fc_processed = true;

				fc_type = (ProcSignalBarrierType) pg_rightmost_one_pos32(fc_flags);
				switch (fc_type)
				{
					case PROCSIGNAL_BARRIER_SMGRRELEASE:
						fc_processed = ProcessBarrierSmgrRelease();
						break;
				}

				/*
				 * 为了避免无限循环，我们必须始终在标志中取消设置位。
				 */
				BARRIER_CLEAR_BIT(fc_flags, fc_type);

				/*
				 * 如果我们未能处理障碍，则重置共享位，以便稍后再试，并设置标志，以便我们不增加代数。
				 */
				if (!fc_processed)
				{
					fc_ResetProcSignalBarrierBits(((uint32) 1) << fc_type);
					fc_success = false;
				}
			}
		}
		PG_CATCH();
		{
			/*
			 * 如果发生错误，我们将需要稍后再次尝试处理该障碍类型以及尚未处理或未成功吸收的其他类型。
			 */
			fc_ResetProcSignalBarrierBits(fc_flags);
			PG_RE_THROW();
		}
		PG_END_TRY();

		/*
		 * 如果某些障碍类型没有成功吸收，我们将需要稍后再试。
		 */
		if (!fc_success)
			return;
	}

	/*
	 * 与所有可能已发出障碍相关的状态变化现已处理完毕，因此我们可以将我们的代数概念更新为我们在开始更新之前观察到的那个。如果事情进一步变化，下次调用该函数时会修复。
	 */
	pg_atomic_write_u64(&MyProcSignalSlot->pss_barrierGeneration, fc_shared_gen);
	ConditionVariableBroadcast(&MyProcSignalSlot->pss_barrierCV);
}

/*
 * 如果我们无法吸收一个或多个障碍类型，无论是由于障碍处理函数返回false还是由于错误，请安排稍后重试处理。
 */
static void fc_ResetProcSignalBarrierBits(uint32 fc_flags)
{
	pg_atomic_fetch_or_u32(&MyProcSignalSlot->pss_barrierCheckMask, fc_flags);
	ProcSignalBarrierPending = true;
	InterruptPending = true;
}

/*
 * CheckProcSignal - 检查是否已发送特定原因的信号，并清除信号标志。在接收SIGUSR1后调用。
 */
static bool fc_CheckProcSignal(ProcSignalReason fc_reason)
{
	volatile ProcSignalSlot *fc_slot = MyProcSignalSlot;

	if (fc_slot != NULL)
	{
		/* 小心 --- 如果我们还没有看到它被设置，不要清除标志 */
		if (fc_slot->pss_signalFlags[fc_reason])
		{
			fc_slot->pss_signalFlags[fc_reason] = false;
			return true;
		}
	}

	return false;
}

/*
 * procsignal_sigusr1_handler - 处理SIGUSR1信号。
 */
void procsignal_sigusr1_handler(SIGNAL_ARGS)
{
	int			fc_save_errno = errno;

	if (fc_CheckProcSignal(PROCSIG_CATCHUP_INTERRUPT))
		HandleCatchupInterrupt();

	if (fc_CheckProcSignal(PROCSIG_NOTIFY_INTERRUPT))
		HandleNotifyInterrupt();

	if (fc_CheckProcSignal(PROCSIG_PARALLEL_MESSAGE))
		HandleParallelMessageInterrupt();

	if (fc_CheckProcSignal(PROCSIG_WALSND_INIT_STOPPING))
		HandleWalSndInitStopping();

	if (fc_CheckProcSignal(PROCSIG_BARRIER))
		fc_HandleProcSignalBarrierInterrupt();

	if (fc_CheckProcSignal(PROCSIG_LOG_MEMORY_CONTEXT))
		HandleLogMemoryContextInterrupt();

	if (fc_CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_DATABASE))
		RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_DATABASE);

	if (fc_CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_TABLESPACE))
		RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_TABLESPACE);

	if (fc_CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_LOCK))
		RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_LOCK);

	if (fc_CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_SNAPSHOT))
		RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_SNAPSHOT);

	if (fc_CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK))
		RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);

	if (fc_CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN))
		RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);

	SetLatch(MyLatch);

	errno = fc_save_errno;
}
